// 这个文件除了第一次的初始化渲染之外，还要做比对操作

// 将虚拟节点渲染成真实节点
export function render(vnode, container){
  let el = createElm(vnode);
  container.appendChild(el);
}

// 递归创建真实节点
function createElm(vnode){
  let { tag, children, key, props, text } = vnode;
  if (typeof tag === 'string') {
    // 这是一个标签
    vnode.el = document.createElement(tag);
    updateProperties(vnode, props);
    children.forEach(child => {
      // 递归渲染当前子节点
      return render(child, vnode.el);
    });
  }else{
    // 文本
    vnode.el = document.createTextNode(text);
  }
  return vnode.el;
}

/**
 * vnode结构
 * {
 *  tag, // 当前的标签名
    props, // 标签属性
    key, // 唯一
    children,
    text
 * }
 * 
 * @param {*} vnode 
 * @param {*} oldProps 
 */
function updateProperties(vnode, oldProps={}){
  let newProps = vnode.props || {}; // 获取当前老节点中的属性
  let el = vnode.el; // 当前的真实节点

  let newStyle = newProps.style || {};
  let oldStyle = oldProps.style || {};


  // 稍后会用到这个更新操作，主要根据新的节点的属性来修改dom元素
  for(let key in oldStyle){
    if (!newStyle[key]) {
      el.style[key] = '';
    }
  }

  // 如果下次更新时，应该用新的属性来更新老的节点
  // 如果老的中有属性，新的中没有
  for(let key in oldProps){
    if (!newProps[key]) {
      // 新的中没有这个属性，就直接删除
      delete el[key];
    }
  }

  for(let key in newProps) {
    if (key === 'style') {
      for(let styleNmae in newProps.style){
        el.style[styleNmae] = newProps.style[styleNmae];
      }
    }else if(key === 'class'){
      el.className = newProps[key];
    }else{
      // 给真实元素添加属性
      el[key] = newProps[key];
    }
    
  }

}


/**
 * 新老节点比对
 */

export function patch(oldVnode, newVnode){
  // 1 比对标签
  if (oldVnode.tag !== newVnode.tag) {
    let newEl = createElm(newVnode)
    // 在真实dom中将老节点替换成新节点,必须拿到父节点来替换
    oldVnode.el.parentNode.replaceChild(newEl, oldVnode.el);
    return
  }

  // 2 比较文本, 文本节点没有tag
  if (!oldVnode.tag) {
    if (oldVnode.text !== newVnode.text) {
      oldVnode.el.textContent = newVnode.text;
    }
  }

  // 3 标签一样，可能属性不一样
  let el = newVnode.el = oldVnode.el; // 标签一样复用即可

  updateProperties(newVnode, oldVnode.props); // 做属性比对

  // 比较孩子

  let oldChildren = oldVnode.children || [];
  let newChildren = newVnode.children || [];

  // 1、老的有孩子， 新的有孩子
  if (oldChildren.length > 0 && newChildren.length > 0) {
    updateChildrenn(el, oldChildren, newChildren);
  }else if (oldChildren.length > 0 && newChildren.length === 0) {
    // 2 老的有孩子，新的没孩子
    el.innerHtml = '';
  }else if (oldChildren.length === 0 && newChildren.length > 0){
    // 3 老的没孩子  新的有孩子
    newChildren.forEach((child, i) => {
      // 将当前新的儿子，放到节点中
      el.appendChild(createElm(child));
    })
  }
  return el;
}

/**
 * 核心 对比新老子节点
 * @param {*} parent 
 * @param {*} oldChildren 
 * @param {*} newChildren 
 */
function updateChildrenn(parent, oldChildren, newChildren){
  // vue增加了很多优化策略， 因为在浏览器中操作dom的最常见的方法 ：在开头和结尾插入
  // 涉及到正序和倒序

  // 老的节点开始和结尾
  let oldStartIndex = 0; 
  let oldStartVnode = oldChildren[0];
  let oldEndIndex = oldChildren.length - 1;
  let oldEndVnode = oldChildren[oldEndIndex];

  // 新的节点开始和结尾
  let newStartIndex = 0;
  let newStartVnode = newChildren[0];
  let newEndIndex = newChildren.length - 1;
  let newEndVnode = newChildren[newEndIndex];

  function makeIndexByKey(children){
    let map = {};
    children.forEach((child, index) => {
      map[child.key] = index;
    })
    return map;
  }
  let map = makeIndexByKey(oldChildren)


  while(oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex){
      if (!oldStartVnode) { // 排除undefined情况
        oldStartVnode = oldChildren[++oldStartIndex];
      }else if (!oldEndVnode) {
        oldEndVnode = oldChildren[--oldEndIndex];
      }else if (isSameVnode(oldStartVnode, newStartVnode)) {
      /**
       * oldVnode   A  B  C  D
       * newVnode   A  B  C  D  E  F
       * 尾部追加 老的开始 和 新的开始 对比
       */
      // 用新的属性更新 节点
      patch(oldStartVnode, newStartVnode);
      oldStartVnode = oldChildren[++oldStartIndex];
      newStartVnode = newChildren[++newStartIndex];
    }else if (isSameVnode(oldEndVnode, newEndVnode)){
      /**
      * oldVnode        A  B  C  D
      * newVnode  E  F  A  B  C  D
      * 头部追加  
      * 开始不相同，老的结尾 和 新的结尾 对比
      */
      patch(oldEndVnode, newEndVnode);
      oldEndVnode = oldChildren[--oldEndIndex];
      newEndVnode = newChildren[--newEndIndex];
    }else if (isSameVnode(oldStartVnode, newEndVnode)) {
      /**
      * oldVnode   A  B  C  D
      * newVnode   B  C  D  A
      * 正序和倒序 老的开始 和 新的结尾 对比
      * 
      */
      patch(oldStartVnode, newEndVnode);
      // 移动元素, 将第一个元素移动到最后
      parent.insertBefore(oldStartVnode.el, oldEndVnode.el.nextSibling);

      oldStartVnode = oldChildren[++oldStartIndex];
      newEndVnode = newChildren[--newEndIndex];
      
    }else if (isSameVnode(oldEndVnode, newStartVnode)) {
      /**
      * oldVnode   A  B  C  D
      * newVnode   D  A  B  C
      * 正序和倒序 老的结尾 和 新的开始 对比
      * 
      */
      patch(oldEndVnode, newStartVnode);
      // 将 D 移动到 A前面
      parent.insertBefore(oldEndVnode.el, oldChildren[oldStartIndex].el);
      oldEndVnode = oldChildren[--oldEndIndex];
      newStartVnode = newChildren[++newStartIndex];
    }else{
      let moveIndex = map[newStartVnode.key];
      if (moveIndex == undefined) {
        // 没有找到, 创建新的
        parent.insertBefore(createElm(newStartVnode), oldStartVnode.el);
      }else{
        // 移动这个元素
        let moveVnode = oldChildren[moveIndex];
        patch(moveVnode, newStartVnode);
        oldChildren[moveIndex] = undefined;
        parent.insertBefore(moveVnode.el, oldStartVnode.el);
      }
      //将新节点的指针后移
      newStartVnode = newChildren[++newStartIndex];
    }
  }

  // 如果到最后还剩余，需要将剩余的插入
  if (newStartIndex <= newEndIndex) {
    for(let i = newStartIndex; i < newChildren.length; i++){
      let ele = newChildren[newEndIndex+1] == null? null: newChildren[newEndIndex+1].el;
      // 在ele前面插入新元素  
      // insertBefore(ele, null)  <=>  appendChild  第二个参数为null等价于appendChild
      parent.insertBefore(createElm(newChildren[i]), ele);
      // 可能是往前面插入，可有可能是往后面插入
      // parent.appendChild(createElm(newChildren[i]));
    }
  }

  // 乱序比较，最后身下的删除
  if(oldStartIndex <= oldEndIndex){
    for(let i = oldStartIndex; i < oldChildren.length; i++){
      let child = oldChildren[i];
      if (child != undefined) {
        parent.removeChild(child.el);
      }
    }
  }

}

//  如果标签和key一样就认为相同
function isSameVnode(oldVnode, newVnode){
  return oldVnode.tag === newVnode.tag && oldVnode.key === newVnode.key
}